home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2000 March / maximum-cd-2000-03.iso / Quake3 Game Source / Q3AGameSource.exe / Main / g_client.c < prev    next >
Encoding:
C/C++ Source or Header  |  2000-01-18  |  26.5 KB  |  1,085 lines

  1. // Copyright (C) 1999-2000 Id Software, Inc.
  2. //
  3. #include "g_local.h"
  4.  
  5. // g_client.c -- client functions that don't happen every frame
  6.  
  7. static vec3_t    playerMins = {-15, -15, -24};
  8. static vec3_t    playerMaxs = {15, 15, 32};
  9.  
  10. /*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial
  11. potential spawning position for deathmatch games.
  12. The first time a player enters the game, they will be at an 'initial' spot.
  13. Targets will be fired when someone spawns in on them.
  14. "nobots" will prevent bots from using this spot.
  15. "nohumans" will prevent non-bots from using this spot.
  16. */
  17. void SP_info_player_deathmatch( gentity_t *ent ) {
  18.     int        i;
  19.  
  20.     G_SpawnInt( "nobots", "0", &i);
  21.     if ( i ) {
  22.         ent->flags |= FL_NO_BOTS;
  23.     }
  24.     G_SpawnInt( "nohumans", "0", &i );
  25.     if ( i ) {
  26.         ent->flags |= FL_NO_HUMANS;
  27.     }
  28. }
  29.  
  30. /*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
  31. equivelant to info_player_deathmatch
  32. */
  33. void SP_info_player_start(gentity_t *ent) {
  34.     ent->classname = "info_player_deathmatch";
  35.     SP_info_player_deathmatch( ent );
  36. }
  37.  
  38. /*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
  39. The intermission will be viewed from this point.  Target an info_notnull for the view direction.
  40. */
  41. void SP_info_player_intermission( gentity_t *ent ) {
  42.  
  43. }
  44.  
  45.  
  46.  
  47. /*
  48. =======================================================================
  49.  
  50.   SelectSpawnPoint
  51.  
  52. =======================================================================
  53. */
  54.  
  55. /*
  56. ================
  57. SpotWouldTelefrag
  58.  
  59. ================
  60. */
  61. qboolean SpotWouldTelefrag( gentity_t *spot ) {
  62.     int            i, num;
  63.     int            touch[MAX_GENTITIES];
  64.     gentity_t    *hit;
  65.     vec3_t        mins, maxs;
  66.  
  67.     VectorAdd( spot->s.origin, playerMins, mins );
  68.     VectorAdd( spot->s.origin, playerMaxs, maxs );
  69.     num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
  70.  
  71.     for (i=0 ; i<num ; i++) {
  72.         hit = &g_entities[touch[i]];
  73.         if ( hit->client && hit->client->ps.stats[STAT_HEALTH] > 0 ) {
  74.             return qtrue;
  75.         }
  76.  
  77.     }
  78.  
  79.     return qfalse;
  80. }
  81.  
  82. /*
  83. ================
  84. SelectNearestDeathmatchSpawnPoint
  85.  
  86. Find the spot that we DON'T want to use
  87. ================
  88. */
  89. #define    MAX_SPAWN_POINTS    128
  90. gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from ) {
  91.     gentity_t    *spot;
  92.     vec3_t        delta;
  93.     float        dist, nearestDist;
  94.     gentity_t    *nearestSpot;
  95.  
  96.     nearestDist = 999999;
  97.     nearestSpot = NULL;
  98.     spot = NULL;
  99.  
  100.     while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
  101.  
  102.         VectorSubtract( spot->s.origin, from, delta );
  103.         dist = VectorLength( delta );
  104.         if ( dist < nearestDist ) {
  105.             nearestDist = dist;
  106.             nearestSpot = spot;
  107.         }
  108.     }
  109.  
  110.     return nearestSpot;
  111. }
  112.  
  113.  
  114. /*
  115. ================
  116. SelectRandomDeathmatchSpawnPoint
  117.  
  118. go to a random point that doesn't telefrag
  119. ================
  120. */
  121. #define    MAX_SPAWN_POINTS    128
  122. gentity_t *SelectRandomDeathmatchSpawnPoint( void ) {
  123.     gentity_t    *spot;
  124.     int            count;
  125.     int            selection;
  126.     gentity_t    *spots[MAX_SPAWN_POINTS];
  127.  
  128.     count = 0;
  129.     spot = NULL;
  130.  
  131.     while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
  132.         if ( SpotWouldTelefrag( spot ) ) {
  133.             continue;
  134.         }
  135.         spots[ count ] = spot;
  136.         count++;
  137.     }
  138.  
  139.     if ( !count ) {    // no spots that won't telefrag
  140.         return G_Find( NULL, FOFS(classname), "info_player_deathmatch");
  141.     }
  142.  
  143.     selection = rand() % count;
  144.     return spots[ selection ];
  145. }
  146.  
  147.  
  148. /*
  149. ===========
  150. SelectSpawnPoint
  151.  
  152. Chooses a player start, deathmatch start, etc
  153. ============
  154. */
  155. gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) {
  156.     gentity_t    *spot;
  157.     gentity_t    *nearestSpot;
  158.  
  159.     nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint );
  160.  
  161.     spot = SelectRandomDeathmatchSpawnPoint ( );
  162.     if ( spot == nearestSpot ) {
  163.         // roll again if it would be real close to point of death
  164.         spot = SelectRandomDeathmatchSpawnPoint ( );
  165.         if ( spot == nearestSpot ) {
  166.             // last try
  167.             spot = SelectRandomDeathmatchSpawnPoint ( );
  168.         }        
  169.     }
  170.  
  171.     // find a single player start spot
  172.     if (!spot) {
  173.         G_Error( "Couldn't find a spawn point" );
  174.     }
  175.  
  176.     VectorCopy (spot->s.origin, origin);
  177.     origin[2] += 9;
  178.     VectorCopy (spot->s.angles, angles);
  179.  
  180.     return spot;
  181. }
  182.  
  183. /*
  184. ===========
  185. SelectInitialSpawnPoint
  186.  
  187. Try to find a spawn point marked 'initial', otherwise
  188. use normal spawn selection.
  189. ============
  190. */
  191. gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles ) {
  192.     gentity_t    *spot;
  193.  
  194.     spot = NULL;
  195.     while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
  196.         if ( spot->spawnflags & 1 ) {
  197.             break;
  198.         }
  199.     }
  200.  
  201.     if ( !spot || SpotWouldTelefrag( spot ) ) {
  202.         return SelectSpawnPoint( vec3_origin, origin, angles );
  203.     }
  204.  
  205.     VectorCopy (spot->s.origin, origin);
  206.     origin[2] += 9;
  207.     VectorCopy (spot->s.angles, angles);
  208.  
  209.     return spot;
  210. }
  211.  
  212. /*
  213. ===========
  214. SelectSpectatorSpawnPoint
  215.  
  216. ============
  217. */
  218. gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) {
  219.     FindIntermissionPoint();
  220.  
  221.     VectorCopy( level.intermission_origin, origin );
  222.     VectorCopy( level.intermission_angle, angles );
  223.  
  224.     return NULL;
  225. }
  226.  
  227. /*
  228. =======================================================================
  229.  
  230. BODYQUE
  231.  
  232. =======================================================================
  233. */
  234.  
  235. /*
  236. ===============
  237. InitBodyQue
  238. ===============
  239. */
  240. void InitBodyQue (void) {
  241.     int        i;
  242.     gentity_t    *ent;
  243.  
  244.     level.bodyQueIndex = 0;
  245.     for (i=0; i<BODY_QUEUE_SIZE ; i++) {
  246.         ent = G_Spawn();
  247.         ent->classname = "bodyque";
  248.         ent->neverFree = qtrue;
  249.         level.bodyQue[i] = ent;
  250.     }
  251. }
  252.  
  253. /*
  254. =============
  255. BodySink
  256.  
  257. After sitting around for five seconds, fall into the ground and dissapear
  258. =============
  259. */
  260. void BodySink( gentity_t *ent ) {
  261.     if ( level.time - ent->timestamp > 6500 ) {
  262.         // the body ques are never actually freed, they are just unlinked
  263.         trap_UnlinkEntity( ent );
  264.         ent->physicsObject = qfalse;
  265.         return;    
  266.     }
  267.     ent->nextthink = level.time + 100;
  268.     ent->s.pos.trBase[2] -= 1;
  269. }
  270.  
  271. /*
  272. =============
  273. CopyToBodyQue
  274.  
  275. A player is respawning, so make an entity that looks
  276. just like the existing corpse to leave behind.
  277. =============
  278. */
  279. void CopyToBodyQue( gentity_t *ent ) {
  280.     gentity_t        *body;
  281.     int            contents;
  282.  
  283.     trap_UnlinkEntity (ent);
  284.  
  285.     // if client is in a nodrop area, don't leave the body
  286.     contents = trap_PointContents( ent->s.origin, -1 );
  287.     if ( contents & CONTENTS_NODROP ) {
  288.         return;
  289.     }
  290.  
  291.     // grab a body que and cycle to the next one
  292.     body = level.bodyQue[ level.bodyQueIndex ];
  293.     level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE;
  294.  
  295.     trap_UnlinkEntity (body);
  296.  
  297.     body->s = ent->s;
  298.     body->s.eFlags = EF_DEAD;        // clear EF_TALK, etc
  299.     body->s.powerups = 0;    // clear powerups
  300.     body->s.loopSound = 0;    // clear lava burning
  301.     body->s.number = body - g_entities;
  302.     body->timestamp = level.time;
  303.     body->physicsObject = qtrue;
  304.     body->physicsBounce = 0;        // don't bounce
  305.     if ( body->s.groundEntityNum == ENTITYNUM_NONE ) {
  306.         body->s.pos.trType = TR_GRAVITY;
  307.         body->s.pos.trTime = level.time;
  308.         VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta );
  309.     } else {
  310.         body->s.pos.trType = TR_STATIONARY;
  311.     }
  312.     body->s.event = 0;
  313.  
  314.     // change the animation to the last-frame only, so the sequence
  315.     // doesn't repeat anew for the body
  316.     switch ( body->s.legsAnim & ~ANIM_TOGGLEBIT ) {
  317.     case BOTH_DEATH1:
  318.     case BOTH_DEAD1:
  319.         body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1;
  320.         break;
  321.     case BOTH_DEATH2:
  322.     case BOTH_DEAD2:
  323.         body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2;
  324.         break;
  325.     case BOTH_DEATH3:
  326.     case BOTH_DEAD3:
  327.     default:
  328.         body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3;
  329.         break;
  330.     }
  331.  
  332.     body->r.svFlags = ent->r.svFlags;
  333.     VectorCopy (ent->r.mins, body->r.mins);
  334.     VectorCopy (ent->r.maxs, body->r.maxs);
  335.     VectorCopy (ent->r.absmin, body->r.absmin);
  336.     VectorCopy (ent->r.absmax, body->r.absmax);
  337.  
  338.     body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP;
  339.     body->r.contents = CONTENTS_CORPSE;
  340.     body->r.ownerNum = ent->r.ownerNum;
  341.  
  342.     body->nextthink = level.time + 5000;
  343.     body->think = BodySink;
  344.  
  345.     body->die = body_die;
  346.  
  347.     // don't take more damage if already gibbed
  348.     if ( ent->health <= GIB_HEALTH ) {
  349.         body->takedamage = qfalse;
  350.     } else {
  351.         body->takedamage = qtrue;
  352.     }
  353.  
  354.  
  355.     VectorCopy ( body->s.pos.trBase, body->r.currentOrigin );
  356.     trap_LinkEntity (body);
  357. }
  358.  
  359. //======================================================================
  360.  
  361.  
  362. /*
  363. ==================
  364. SetClientViewAngle
  365.  
  366. ==================
  367. */
  368. void SetClientViewAngle( gentity_t *ent, vec3_t angle ) {
  369.     int            i;
  370.  
  371.     // set the delta angle
  372.     for (i=0 ; i<3 ; i++) {
  373.         int        cmdAngle;
  374.  
  375.         cmdAngle = ANGLE2SHORT(angle[i]);
  376.         ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i];
  377.     }
  378.     VectorCopy( angle, ent->s.angles );
  379.     VectorCopy (ent->s.angles, ent->client->ps.viewangles);
  380. }
  381.  
  382. /*
  383. ================
  384. respawn
  385. ================
  386. */
  387. void respawn( gentity_t *ent ) {
  388.     gentity_t    *tent;
  389.  
  390.     CopyToBodyQue (ent);
  391.     ClientSpawn(ent);
  392.  
  393.     // add a teleportation effect
  394.     tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN );
  395.     tent->s.clientNum = ent->s.clientNum;
  396. }
  397.  
  398. /*
  399. ================
  400. TeamCount
  401.  
  402. Returns number of players on a team
  403. ================
  404. */
  405. team_t TeamCount( int ignoreClientNum, int team ) {
  406.     int        i;
  407.     int        count = 0;
  408.  
  409.     for ( i = 0 ; i < level.maxclients ; i++ ) {
  410.         if ( i == ignoreClientNum ) {
  411.             continue;
  412.         }
  413.         if ( level.clients[i].pers.connected == CON_DISCONNECTED ) {
  414.             continue;
  415.         }
  416.         if ( level.clients[i].sess.sessionTeam == team ) {
  417.             count++;
  418.         }
  419.     }
  420.  
  421.     return count;
  422. }
  423.  
  424.  
  425. /*
  426. ================
  427. PickTeam
  428.  
  429. ================
  430. */
  431. team_t PickTeam( int ignoreClientNum ) {
  432.     int        counts[TEAM_NUM_TEAMS];
  433.  
  434.     counts[TEAM_BLUE] = TeamCount( ignoreClientNum, TEAM_BLUE );
  435.     counts[TEAM_RED] = TeamCount( ignoreClientNum, TEAM_RED );
  436.  
  437.     if ( counts[TEAM_BLUE] > counts[TEAM_RED] ) {
  438.         return TEAM_RED;
  439.     }
  440.     if ( counts[TEAM_RED] > counts[TEAM_BLUE] ) {
  441.         return TEAM_BLUE;
  442.     }
  443.     // equal team count, so join the team with the lowest score
  444.     if ( level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED] ) {
  445.         return TEAM_RED;
  446.     }
  447.     return TEAM_BLUE;
  448. }
  449.  
  450. /*
  451. ===========
  452. ForceClientSkin
  453.  
  454. Forces a client's skin (for teamplay)
  455. ===========
  456. */
  457. void ForceClientSkin( gclient_t *client, char *model, const char *skin ) {
  458.     char *p;
  459.  
  460.     if ((p = strchr(model, '/')) != NULL) {
  461.         *p = 0;
  462.     }
  463.  
  464.     Q_strcat(model, MAX_QPATH, "/");
  465.     Q_strcat(model, MAX_QPATH, skin);
  466. }
  467.  
  468.  
  469. /*
  470. ===========
  471. ClientCheckName
  472. ============
  473. */
  474. static void ClientCleanName( const char *in, char *out, int outSize ) {
  475.     int        len, colorlessLen;
  476.     char    ch;
  477.     char    *p;
  478.     int        spaces;
  479.  
  480.     //save room for trailing null byte
  481.     outSize--;
  482.  
  483.     len = 0;
  484.     colorlessLen = 0;
  485.     p = out;
  486.     *p = 0;
  487.     spaces = 0;
  488.  
  489.     while( 1 ) {
  490.         ch = *in++;
  491.         if( !ch ) {
  492.             break;
  493.         }
  494.  
  495.         // don't allow leading spaces
  496.         if( !*p && ch == ' ' ) {
  497.             continue;
  498.         }
  499.  
  500.         // check colors
  501.         if( ch == Q_COLOR_ESCAPE ) {
  502.             // solo trailing carat is not a color prefix
  503.             if( !*in ) {
  504.                 break;
  505.             }
  506.  
  507.             // don't allow black in a name, period
  508.             if( ColorIndex(*in) == 0 ) {
  509.                 in++;
  510.                 continue;
  511.             }
  512.  
  513.             // make sure room in dest for both chars
  514.             if( len > outSize - 2 ) {
  515.                 break;
  516.             }
  517.  
  518.             *out++ = ch;
  519.             *out++ = *in++;
  520.             len += 2;
  521.             continue;
  522.         }
  523.  
  524.         // don't allow too many consecutive spaces
  525.         if( ch == ' ' ) {
  526.             spaces++;
  527.             if( spaces > 3 ) {
  528.                 continue;
  529.             }
  530.         }
  531.         else {
  532.             spaces = 0;
  533.         }
  534.  
  535.         if( len > outSize - 1 ) {
  536.             break;
  537.         }
  538.  
  539.         *out++ = ch;
  540.         colorlessLen++;
  541.         len++;
  542.     }
  543.     *out = 0;
  544.  
  545.     // don't allow empty names
  546.     if( *p == 0 || colorlessLen == 0 ) {
  547.         Q_strncpyz( p, "UnnamedPlayer", outSize );
  548.     }
  549. }
  550.  
  551.  
  552. /*
  553. ===========
  554. ClientUserInfoChanged
  555.  
  556. Called from ClientConnect when the player first connects and
  557. directly by the server system when the player updates a userinfo variable.
  558.  
  559. The game can override any of the settings and call trap_SetUserinfo
  560. if desired.
  561. ============
  562. */
  563. void ClientUserinfoChanged( int clientNum ) {
  564.     gentity_t *ent;
  565.     char    *s;
  566.     char    model[MAX_QPATH];
  567.     char    oldname[MAX_STRING_CHARS];
  568.     gclient_t    *client;
  569.     char    *c1;
  570.     char    userinfo[MAX_INFO_STRING];
  571.  
  572.     ent = g_entities + clientNum;
  573.     client = ent->client;
  574.  
  575.     trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
  576.  
  577.     // check for malformed or illegal info strings
  578.     if ( !Info_Validate(userinfo) ) {
  579.         strcpy (userinfo, "\\name\\badinfo");
  580.     }
  581.  
  582.     // check for local client
  583.     s = Info_ValueForKey( userinfo, "ip" );
  584.     if ( !strcmp( s, "localhost" ) ) {
  585.         client->pers.localClient = qtrue;
  586.     }
  587.  
  588.     // check the item prediction
  589.     s = Info_ValueForKey( userinfo, "cg_predictItems" );
  590.     if ( !atoi( s ) ) {
  591.         client->pers.predictItemPickup = qfalse;
  592.     } else {
  593.         client->pers.predictItemPickup = qtrue;
  594.     }
  595.  
  596.     // set name
  597.     Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) );
  598.     s = Info_ValueForKey (userinfo, "name");
  599.     ClientCleanName( s, client->pers.netname, sizeof(client->pers.netname) );
  600.  
  601.     if ( client->sess.sessionTeam == TEAM_SPECTATOR ) {
  602.         if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) {
  603.             Q_strncpyz( client->pers.netname, "scoreboard", sizeof(client->pers.netname) );
  604.         }
  605.     }
  606.  
  607.     if ( client->pers.connected == CON_CONNECTED ) {
  608.         if ( strcmp( oldname, client->pers.netname ) ) {
  609.             trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname, 
  610.                 client->pers.netname) );
  611.         }
  612.     }
  613.  
  614.     // set max health
  615.     client->pers.maxHealth = atoi( Info_ValueForKey( userinfo, "handicap" ) );
  616.     if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) {
  617.         client->pers.maxHealth = 100;
  618.     }
  619.     client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;
  620.  
  621.     // set model
  622.     Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) );
  623.  
  624.     // team
  625.     switch( client->sess.sessionTeam ) {
  626.     case TEAM_RED:
  627.         ForceClientSkin(client, model, "red");
  628.         break;
  629.     case TEAM_BLUE:
  630.         ForceClientSkin(client, model, "blue");
  631.         break;
  632.     }
  633.     if ( g_gametype.integer >= GT_TEAM && client->sess.sessionTeam == TEAM_SPECTATOR ) {
  634.         // don't ever use a default skin in teamplay, it would just waste memory
  635.         ForceClientSkin(client, model, "red");
  636.     }
  637.  
  638.  
  639.     // colors
  640.     c1 = Info_ValueForKey( userinfo, "color" );
  641.  
  642.     // teamInfo
  643.     s = Info_ValueForKey( userinfo, "teamoverlay" );
  644.     if ( ! *s || atoi( s ) != 0 ) {
  645.         client->pers.teamInfo = qtrue;
  646.     } else {
  647.         client->pers.teamInfo = qfalse;
  648.     }
  649.  
  650.     // send over a subset of the userinfo keys so other clients can
  651.     // print scoreboards, display models, and play custom sounds
  652.     if ( ent->r.svFlags & SVF_BOT ) {
  653.         s = va("n\\%s\\t\\%i\\model\\%s\\c1\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s",
  654.             client->pers.netname, client->sess.sessionTeam, model, c1,
  655.             client->pers.maxHealth, client->sess.wins, client->sess.losses,
  656.             Info_ValueForKey( userinfo, "skill" ) );
  657.     } else {
  658.         s = va("n\\%s\\t\\%i\\model\\%s\\c1\\%s\\hc\\%i\\w\\%i\\l\\%i",
  659.             client->pers.netname, client->sess.sessionTeam, model, c1,
  660.             client->pers.maxHealth, client->sess.wins, client->sess.losses );
  661.     }
  662.  
  663.     trap_SetConfigstring( CS_PLAYERS+clientNum, s );
  664.  
  665.     G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s );
  666. }
  667.  
  668.  
  669. /*
  670. ===========
  671. ClientConnect
  672.  
  673. Called when a player begins connecting to the server.
  674. Called again for every map change or tournement restart.
  675.  
  676. The session information will be valid after exit.
  677.  
  678. Return NULL if the client should be allowed, otherwise return
  679. a string with the reason for denial.
  680.  
  681. Otherwise, the client will be sent the current gamestate
  682. and will eventually get to ClientBegin.
  683.  
  684. firstTime will be qtrue the very first time a client connects
  685. to the server machine, but qfalse on map changes and tournement
  686. restarts.
  687. ============
  688. */
  689. char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) {
  690.     char        *value;
  691.     gclient_t    *client;
  692.     char        userinfo[MAX_INFO_STRING];
  693.     gentity_t    *ent;
  694.  
  695.     ent = &g_entities[ clientNum ];
  696.  
  697.     trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
  698.  
  699.     // check to see if they are on the banned IP list
  700.     value = Info_ValueForKey (userinfo, "ip");
  701.     if ( G_FilterPacket( value ) ) {
  702.         return "Banned.";
  703.     }
  704.  
  705.     // check for a password
  706.     value = Info_ValueForKey (userinfo, "password");
  707.     if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) &&
  708.         strcmp( g_password.string, value) != 0) {
  709.         return "Invalid password";
  710.     }
  711.  
  712.     // they can connect
  713.     ent->client = level.clients + clientNum;
  714.     client = ent->client;
  715.  
  716.     memset( client, 0, sizeof(*client) );
  717.  
  718.     client->pers.connected = CON_CONNECTING;
  719.  
  720.     // read or initialize the session data
  721.     if ( firstTime || level.newSession ) {
  722.         G_InitSessionData( client, userinfo );
  723.     }
  724.     G_ReadSessionData( client );
  725.  
  726.     if( isBot ) {
  727.         ent->r.svFlags |= SVF_BOT;
  728.         ent->inuse = qtrue;
  729.         if( !G_BotConnect( clientNum, !firstTime ) ) {
  730.             return "BotConnectfailed";
  731.         }
  732.     }
  733.  
  734.     // get and distribute relevent paramters
  735.     G_LogPrintf( "ClientConnect: %i\n", clientNum );
  736.     ClientUserinfoChanged( clientNum );
  737.  
  738.     // don't do the "xxx connected" messages if they were caried over from previous level
  739.     if ( firstTime ) {
  740.         trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname) );
  741.     }
  742.  
  743.     if ( g_gametype.integer >= GT_TEAM &&
  744.         client->sess.sessionTeam != TEAM_SPECTATOR ) {
  745.         BroadcastTeamChange( client, -1 );
  746.     }
  747.  
  748.     // count current clients and rank for scoreboard
  749.     CalculateRanks();
  750.  
  751.     return NULL;
  752. }
  753.  
  754. /*
  755. ===========
  756. ClientBegin
  757.  
  758. called when a client has finished connecting, and is ready
  759. to be placed into the level.  This will happen every level load,
  760. and on transition between teams, but doesn't happen on respawns
  761. ============
  762. */
  763. void ClientBegin( int clientNum ) {
  764.     gentity_t    *ent;
  765.     gclient_t    *client;
  766.     gentity_t    *tent;
  767.     int            flags;
  768.  
  769.     ent = g_entities + clientNum;
  770.  
  771.     if( ent->botDelayBegin ) {
  772.         G_QueueBotBegin( clientNum );
  773.         ent->botDelayBegin = qfalse;
  774.         return;
  775.     }
  776.  
  777.     client = level.clients + clientNum;
  778.  
  779.     if ( ent->r.linked ) {
  780.         trap_UnlinkEntity( ent );
  781.     }
  782.     G_InitGentity( ent );
  783.     ent->touch = 0;
  784.     ent->pain = 0;
  785.     ent->client = client;
  786.  
  787.     client->pers.connected = CON_CONNECTED;
  788.     client->pers.enterTime = level.time;
  789.     client->pers.teamState.state = TEAM_BEGIN;
  790.  
  791.     // save eflags around this, because changing teams will
  792.     // cause this to happen with a valid entity, and we
  793.     // want to make sure the teleport bit is set right
  794.     // so the viewpoint doesn't interpolate through the
  795.     // world to the new position
  796.     flags = client->ps.eFlags;
  797.     memset( &client->ps, 0, sizeof( client->ps ) );
  798.     client->ps.eFlags = flags;
  799.  
  800.     // locate ent at a spawn point
  801.     ClientSpawn( ent );
  802.  
  803.     if ( client->sess.sessionTeam != TEAM_SPECTATOR ) {
  804.         // send event
  805.         tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN );
  806.         tent->s.clientNum = ent->s.clientNum;
  807.  
  808.         if ( g_gametype.integer != GT_TOURNAMENT ) {
  809.             trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname) );
  810.         }
  811.     }
  812.     G_LogPrintf( "ClientBegin: %i\n", clientNum );
  813.  
  814.     // count current clients and rank for scoreboard
  815.     CalculateRanks();
  816. }
  817.  
  818. /*
  819. ===========
  820. ClientSpawn
  821.  
  822. Called every time a client is placed fresh in the world:
  823. after the first ClientBegin, and after each respawn
  824. Initializes all non-persistant parts of playerState
  825. ============
  826. */
  827. void ClientSpawn(gentity_t *ent) {
  828.     int        index;
  829.     vec3_t    spawn_origin, spawn_angles;
  830.     gclient_t    *client;
  831.     int        i;
  832.     clientPersistant_t    saved;
  833.     clientSession_t        savedSess;
  834.     int        persistant[MAX_PERSISTANT];
  835.     gentity_t    *spawnPoint;
  836.     int        flags;
  837.     int        savedPing;
  838.  
  839.     index = ent - g_entities;
  840.     client = ent->client;
  841.  
  842.     // find a spawn point
  843.     // do it before setting health back up, so farthest
  844.     // ranging doesn't count this client
  845.     if ( client->sess.sessionTeam == TEAM_SPECTATOR ) {
  846.         spawnPoint = SelectSpectatorSpawnPoint ( 
  847.             spawn_origin, spawn_angles);
  848.     } else if (g_gametype.integer == GT_CTF) {
  849.         spawnPoint = SelectCTFSpawnPoint ( 
  850.             client->sess.sessionTeam, 
  851.             client->pers.teamState.state, 
  852.             spawn_origin, spawn_angles);
  853.     } else {
  854.         do {
  855.             // the first spawn should be at a good looking spot
  856.             if ( !client->pers.initialSpawn && client->pers.localClient ) {
  857.                 client->pers.initialSpawn = qtrue;
  858.                 spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles );
  859.             } else {
  860.                 // don't spawn near existing origin if possible
  861.                 spawnPoint = SelectSpawnPoint ( 
  862.                     client->ps.origin, 
  863.                     spawn_origin, spawn_angles);
  864.             }
  865.  
  866.             // Tim needs to prevent bots from spawning at the initial point
  867.             // on q3dm0...
  868.             if ( ( spawnPoint->flags & FL_NO_BOTS ) && ( ent->r.svFlags & SVF_BOT ) ) {
  869.                 continue;    // try again
  870.             }
  871.             // just to be symetric, we have a nohumans option...
  872.             if ( ( spawnPoint->flags & FL_NO_HUMANS ) && !( ent->r.svFlags & SVF_BOT ) ) {
  873.                 continue;    // try again
  874.             }
  875.  
  876.             break;
  877.  
  878.         } while ( 1 );
  879.     }
  880.     client->pers.teamState.state = TEAM_ACTIVE;
  881.  
  882.  
  883.     // toggle the teleport bit so the client knows to not lerp
  884.     flags = ent->client->ps.eFlags & EF_TELEPORT_BIT;
  885.     flags ^= EF_TELEPORT_BIT;
  886.  
  887.     // clear everything but the persistant data
  888.  
  889.     saved = client->pers;
  890.     savedSess = client->sess;
  891.     savedPing = client->ps.ping;
  892.     for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) {
  893.         persistant[i] = client->ps.persistant[i];
  894.     }
  895.     memset (client, 0, sizeof(*client));
  896.  
  897.     client->pers = saved;
  898.     client->sess = savedSess;
  899.     client->ps.ping = savedPing;
  900.     for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) {
  901.         client->ps.persistant[i] = persistant[i];
  902.     }
  903.  
  904.     // increment the spawncount so the client will detect the respawn
  905.     client->ps.persistant[PERS_SPAWN_COUNT]++;
  906.     client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam;
  907.  
  908.     client->airOutTime = level.time + 12000;
  909.  
  910.     // clear entity values
  911.     client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;
  912.     client->ps.eFlags = flags;
  913.  
  914.     ent->s.groundEntityNum = ENTITYNUM_NONE;
  915.     ent->client = &level.clients[index];
  916.     ent->takedamage = qtrue;
  917.     ent->inuse = qtrue;
  918.     ent->classname = "player";
  919.     ent->r.contents = CONTENTS_BODY;
  920.     ent->clipmask = MASK_PLAYERSOLID;
  921.     ent->die = player_die;
  922.     ent->waterlevel = 0;
  923.     ent->watertype = 0;
  924.     ent->flags = 0;
  925.     
  926.     VectorCopy (playerMins, ent->r.mins);
  927.     VectorCopy (playerMaxs, ent->r.maxs);
  928.  
  929.     client->ps.clientNum = index;
  930.  
  931.     client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN );
  932.     if ( g_gametype.integer == GT_TEAM ) {
  933.         client->ps.ammo[WP_MACHINEGUN] = 50;
  934.     } else {
  935.         client->ps.ammo[WP_MACHINEGUN] = 100;
  936.     }
  937.  
  938.     client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GAUNTLET );
  939.     client->ps.ammo[WP_GAUNTLET] = -1;
  940.     client->ps.ammo[WP_GRAPPLING_HOOK] = -1;
  941.  
  942.     // health will count down towards max_health
  943.     ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] * 1.25;
  944.  
  945.     G_SetOrigin( ent, spawn_origin );
  946.     VectorCopy( spawn_origin, client->ps.origin );
  947.  
  948.     // the respawned flag will be cleared after the attack and jump keys come up
  949.     client->ps.pm_flags |= PMF_RESPAWNED;
  950.  
  951.     trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd );
  952.     SetClientViewAngle( ent, spawn_angles );
  953.  
  954.     if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
  955.  
  956.     } else {
  957.         G_KillBox( ent );
  958.         trap_LinkEntity (ent);
  959.  
  960.         // force the base weapon up
  961.         client->ps.weapon = WP_MACHINEGUN;
  962.         client->ps.weaponstate = WEAPON_READY;
  963.  
  964.     }
  965.  
  966.     // don't allow full run speed for a bit
  967.     client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
  968.     client->ps.pm_time = 100;
  969.  
  970.     client->respawnTime = level.time;
  971.     client->inactivityTime = level.time + g_inactivity.integer * 1000;
  972.     client->latched_buttons = 0;
  973.  
  974.     // set default animations
  975.     client->ps.torsoAnim = TORSO_STAND;
  976.     client->ps.legsAnim = LEGS_IDLE;
  977.  
  978.     if ( level.intermissiontime ) {
  979.         MoveClientToIntermission( ent );
  980.     } else {
  981.         // fire the targets of the spawn point
  982.         G_UseTargets( spawnPoint, ent );
  983.  
  984.         // select the highest weapon number available, after any
  985.         // spawn given items have fired
  986.         client->ps.weapon = 1;
  987.         for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) {
  988.             if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) {
  989.                 client->ps.weapon = i;
  990.                 break;
  991.             }
  992.         }
  993.     }
  994.  
  995.     // run a client frame to drop exactly to the floor,
  996.     // initialize animations and other things
  997.     client->ps.commandTime = level.time - 100;
  998.     ent->client->pers.cmd.serverTime = level.time;
  999.     ClientThink( ent-g_entities );
  1000.  
  1001.     // positively link the client, even if the command times are weird
  1002.     if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) {
  1003.         BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue );
  1004.         VectorCopy( ent->client->ps.origin, ent->r.currentOrigin );
  1005.         trap_LinkEntity( ent );
  1006.     }
  1007.  
  1008.     // run the presend to set anything else
  1009.     ClientEndFrame( ent );
  1010.  
  1011.     // clear entity state values
  1012.     BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue );
  1013. }
  1014.  
  1015.  
  1016. /*
  1017. ===========
  1018. ClientDisconnect
  1019.  
  1020. Called when a player drops from the server.
  1021. Will not be called between levels.
  1022.  
  1023. This should NOT be called directly by any game logic,
  1024. call trap_DropClient(), which will call this and do
  1025. server system housekeeping.
  1026. ============
  1027. */
  1028. void ClientDisconnect( int clientNum ) {
  1029.     gentity_t    *ent;
  1030.     gentity_t    *tent;
  1031.     int            i;
  1032.  
  1033.     ent = g_entities + clientNum;
  1034.     if ( !ent->client ) {
  1035.         return;
  1036.     }
  1037.  
  1038.     // stop any following clients
  1039.     for ( i = 0 ; i < level.maxclients ; i++ ) {
  1040.         if ( level.clients[i].sess.sessionTeam == TEAM_SPECTATOR
  1041.             && level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW
  1042.             && level.clients[i].sess.spectatorClient == clientNum ) {
  1043.             StopFollowing( &g_entities[i] );
  1044.         }
  1045.     }
  1046.  
  1047.     // send effect if they were completely connected
  1048.     if ( ent->client->pers.connected == CON_CONNECTED 
  1049.         && ent->client->sess.sessionTeam != TEAM_SPECTATOR ) {
  1050.         tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
  1051.         tent->s.clientNum = ent->s.clientNum;
  1052.  
  1053.         // They don't get to take powerups with them!
  1054.         // Especially important for stuff like CTF flags
  1055.         TossClientItems ( ent );
  1056.     }
  1057.  
  1058.     G_LogPrintf( "ClientDisconnect: %i\n", clientNum );
  1059.  
  1060.     // if we are playing in tourney mode and losing, give a win to the other player
  1061.     if ( g_gametype.integer == GT_TOURNAMENT && !level.intermissiontime
  1062.         && !level.warmupTime && level.sortedClients[1] == clientNum ) {
  1063.         level.clients[ level.sortedClients[0] ].sess.wins++;
  1064.         ClientUserinfoChanged( level.sortedClients[0] );
  1065.     }
  1066.  
  1067.     trap_UnlinkEntity (ent);
  1068.     ent->s.modelindex = 0;
  1069.     ent->inuse = qfalse;
  1070.     ent->classname = "disconnected";
  1071.     ent->client->pers.connected = CON_DISCONNECTED;
  1072.     ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE;
  1073.     ent->client->sess.sessionTeam = TEAM_FREE;
  1074.  
  1075.     trap_SetConfigstring( CS_PLAYERS + clientNum, "");
  1076.  
  1077.     CalculateRanks();
  1078.  
  1079.     if ( ent->r.svFlags & SVF_BOT ) {
  1080.         BotAIShutdownClient( clientNum );
  1081.     }
  1082. }
  1083.  
  1084.  
  1085.